/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package pl.tewi.annotations.processors;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.tewi.contexts.ApplicationServerContext;
import pl.tewi.annotations.Application;
import pl.tewi.annotations.Applications;
import pl.tewi.annotations.Module;
import pl.tewi.exceptions.*;
import pl.tewi.types.ApplicationDesc;
import pl.tewi.types.ModuleDesc;
import pl.tewi.utils.FileUtils;

/**
 * Klasa przetwarzająca adnotację {@link pl.tewi.jeeunit.annotations.Applications}
 * @author Marcin Kwapisz
 */
public class ApplicationsAnnotationProcessor implements AnnotationProcessor {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationsAnnotationProcessor.class);

    /**
     * Metoda przetwarza adnotację {@link pl.tewi.jeeunit.annotations.Applications}
     * @param klass klasa opisana przetwarzaną adnotacją
     * @param context kontekst serwera aplikcyjnego (nie może być null)
     * @return uzupełniony kontekst serwera aplikacyjnego
     * @throws AnnotationProcessingException 
     * @throws GeneralFrameworkException problem z działaniem narzędzia
     */
    @Override
    public ApplicationServerContext processAnnotations(final Class<?> klass, final ApplicationServerContext context) throws AnnotationProcessingException, GeneralFrameworkException {
        LOGGER.debug("Processing {} annotation", Application.class.getName());
        Applications applicationsAnnotation = klass.getAnnotation(Applications.class);
        Application[] applications = applicationsAnnotation.applications();
        ApplicationDesc applicationDesc;

        for (Application app : applications) {
            applicationDesc = processApplication(app);
            //sprawdź czy nazwa serwera context jest taka sama jak nazwa serwera docelowego
            if (context.getInstanceName() != null && !context.getInstanceName().equals(applicationDesc.getTarget())) {
                throw new PrepareConfigurationException(PrepareConfigurationException.INCORRECT_TARGET_INSTANCE);
            }
            if (!context.addApplication(applicationDesc)) {
                throw new ApplicationsAnnotationException(ApplicationsAnnotationException.APP_NAME_ALREADY_EXISTS);
            }
        }
        return context;
    }

    /**
     * Przetwarza adnotacje {@link Application}
     * @param application adnotacja opisu aplikacji 
     * @return opis aplikacji
     * @throws ApplicationsAnnotationException  błędne parametry w adnotacji {@link Application}
     * @throws FileOrDirectoryException problem z konfiguracja sciezek
     */
    private ApplicationDesc processApplication(final Application application) throws AnnotationProcessingException {
        validateApplicationAnnotation(application);
        LOGGER.debug("Processing {} application description", application.name());

        ArrayList<String> descriptors = new ArrayList<String>(Arrays.asList(application.descriptors()));

        Set<ModuleDesc> modulesDesc = new HashSet<ModuleDesc>();
        ModuleDesc moduleDesc;
        for (Module module : application.modules()) {
            moduleDesc = processModule(module);
            //validacja
            if (!modulesDesc.add(moduleDesc)) {
                throw new ApplicationsAnnotationException(ApplicationsAnnotationException.APP_NAME_ALREADY_EXISTS);
            }
        }
        return new ApplicationDesc(application.target(), application.name(), application.applicationPath(), application.applicationType(), modulesDesc, descriptors);
    }

    /**
     * przetwarza adotacje {@link Module}
     * @param module adnotacja opisu modułu
     * @return opis modułu aplikacji 
     * @throws ApplicationsAnnotationException błędne parametry w adnotacji {@link Module}
     */
    private ModuleDesc processModule(final Module module) throws AnnotationProcessingException {
        validateModuleAnnotation(module);
        LOGGER.debug("Processing {} module description", module.name());

        ArrayList<String> descriptors = new ArrayList<String>(Arrays.asList(module.descriptors()));

        return new ModuleDesc(module.name(), module.modulePath(), module.moduleType(), descriptors);
    }

    /**
     * sprawdza poprawność aplikacji
     * @param application
     * @throws AnnotationProcessingException 
     */
    private void validateApplicationAnnotation(final Application application) throws AnnotationProcessingException {
        LOGGER.debug("Validating application annotation");
        if (application.target() == null || application.target().isEmpty()) {
            throw new ApplicationsAnnotationException(ApplicationsAnnotationException.NULL_OR_EMPTY_VALUE + "target");
        }
        if (application.name() == null || application.name().isEmpty()) {
            throw new ApplicationsAnnotationException(ApplicationsAnnotationException.NULL_OR_EMPTY_VALUE + "application name");
        }
        if (application.applicationPath() == null || application.applicationPath().isEmpty()) {
            throw new ApplicationsAnnotationException(ApplicationsAnnotationException.NULL_OR_EMPTY_VALUE + "application basePath");
        }

        if (!FileUtils.checkPath(application.applicationPath())) {
            throw new FileOrDirectoryException(FileOrDirectoryException.INCORRECT_FILE_PATH + application.applicationPath());
        }

        if (!FileUtils.isFileExists(application.applicationPath())) {
            throw new FileOrDirectoryException(FileOrDirectoryException.PATH_NOT_EXISTS + application.applicationPath());
        }

        if (application.modules().length == 0) {
            throw new ApplicationsAnnotationException(ApplicationsAnnotationException.NULL_OR_EMPTY_VALUE + "modules");
        }
        checkDeploymentDescriptors(application.descriptors());
    }

    /**
     * Sprawdza poprawność modułu
     * @param module
     * @throws AnnotationProcessingException 
     */
    private void validateModuleAnnotation(final Module module) throws AnnotationProcessingException {
        LOGGER.debug("Validating module annotation");
        if (module.name() == null || module.name().isEmpty()) {
            throw new ApplicationsAnnotationException(ApplicationsAnnotationException.NULL_OR_EMPTY_VALUE + "module name");
        }
        if (module.modulePath() == null || module.modulePath().isEmpty()) {
            throw new ApplicationsAnnotationException(ApplicationsAnnotationException.NULL_OR_EMPTY_VALUE + "module path");
        }

        if (!FileUtils.checkPath(module.modulePath())) {
            throw new FileOrDirectoryException(FileOrDirectoryException.INCORRECT_FILE_PATH + module.modulePath());
        }

        if (!FileUtils.isFileExists(module.modulePath())) {
            throw new FileOrDirectoryException(FileOrDirectoryException.PATH_NOT_EXISTS + module.modulePath());
        }
        checkDeploymentDescriptors(module.descriptors());
    }

    /**
     * Sprawdza istnienie deskryptorów instalacyjnych
     * @param descriptors
     * @throws FileOrDirectoryException 
     */
    private void checkDeploymentDescriptors(final String[] descriptors) throws FileOrDirectoryException {
        LOGGER.debug("Checking deployment descriptors");
        //sprawdź wszystkie deskryptory instalacyjne
        for (String descriptor : descriptors) {
            LOGGER.debug(descriptor);
            if (!FileUtils.checkPath(descriptor)) {
                throw new FileOrDirectoryException(FileOrDirectoryException.INCORRECT_FILE_PATH + descriptor);
            }
            if (!FileUtils.isFileExists(descriptor)) {
                throw new FileOrDirectoryException(FileOrDirectoryException.PATH_NOT_EXISTS + descriptor);
            }
        }
    }
}
